﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Reflection;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel;
using System.IO;
using System.Xml;
using System.Transactions;
using System.Data;

namespace Defect_Tracking_System.Models
{
    public class ProjectManagement
    {
        private static string Path = AppDomain.CurrentDomain.BaseDirectory + "Content/LockProject.xml";
        private static ProjectManagement instance;

        private ProjectManagement()
        {
        }

        public static ProjectManagement GetInstance()
        {
            if (instance == null)
            {
                instance = new ProjectManagement();
            }
            return instance;
        }

        public IQueryable<Project> GetProjects(string UserName)
        {
            DefectTrackingSystemEntities Entities = new DefectTrackingSystemEntities();

            Account Acc = AccountManagement.GetInstance().GetUserByUserName(UserName);
            return Entities.Projects.Where(m => m.Account.GroupId == Acc.GroupId).OrderBy(p => p.ProjectName);
        }

        #region ProjectLookups
        public string[] GetDefectTypes(String ProjectId)
        {
            DefectTrackingSystemEntities Entities = new DefectTrackingSystemEntities();

            Guid Pid = Guid.Parse(ProjectId);
            var Dts = from Dt in Entities.DefectTypes
                      where Dt.ProjectId == Pid
                      select Dt.TypeName;
            return Dts.ToArray();
        }

        public string[] GetDefectStatuses(String ProjectId)
        {
            DefectTrackingSystemEntities Entities = new DefectTrackingSystemEntities();

            Guid Pid = Guid.Parse(ProjectId);
            var Dss = from Ds in Entities.DefectStatus
                      where Ds.ProjectId == Pid
                      select Ds.Status;

            return Dss.ToArray();
        }

        public string[] GetDefectPriorities(String ProjectId)
        {
            DefectTrackingSystemEntities Entities = new DefectTrackingSystemEntities();

            Guid Pid = Guid.Parse(ProjectId);
            var Dps = from Dp in Entities.DefectPriorities
                      where Dp.ProjectId == Pid
                      select Dp.Priority;

            return Dps.ToArray();
        }

        public string[] GetProjectVersions(String ProjectId)
        {
            DefectTrackingSystemEntities Entities = new DefectTrackingSystemEntities();

            Guid Pid = Guid.Parse(ProjectId);
            var Pvs = from Pv in Entities.ProjectVersions
                      where Pv.ProjectId == Pid
                      select Pv.VersionName;

            return Pvs.ToArray();
        }

        public LookupsModel GetLookups(string ProjectId)
        {
            LookupsModel Lm = new LookupsModel();
            Lm.ProjectId = ProjectId;
            Lm.DefectPriorities = GetDefectPriorities(ProjectId);
            Lm.DefectStatuses = GetDefectStatuses(ProjectId);
            Lm.DefectTypes = GetDefectTypes(ProjectId);
            Lm.ProjectVersions = GetProjectVersions(ProjectId);
            return Lm;
        }

        public void GetAllLookups(string Type, string[] Values, ref List<Object> Li)
        {
            foreach (String Value in Values)
                Li.Add(new { Type = Type, Value = Value });
        }

        public bool AddLookups(LookupType Type, string Value, String ProjectId)
        {
            DefectTrackingSystemEntities Entities = new DefectTrackingSystemEntities();

            Guid Pid = Guid.Parse(ProjectId);
            switch (Type)
            {
                case LookupType.Type:
                    if (Entities.DefectTypes.Any(m => m.TypeName == Value && m.ProjectId == Pid))
                        throw new Exception("This type was existed");
                    DefectType Dt = new DefectType();
                    Dt.TypeName = Value;
                    Dt.ProjectId = Pid;
                    Entities.DefectTypes.AddObject(Dt);
                    Entities.SaveChanges();
                    break;
                case LookupType.Status:
                    if (Entities.DefectStatus.Any(m => m.Status == Value && m.ProjectId == Pid))
                        throw new Exception("This status was existed");
                    DefectStatus Ds = new DefectStatus();
                    Ds.Status = Value;
                    Ds.ProjectId = Pid;
                    Entities.DefectStatus.AddObject(Ds);
                    Entities.SaveChanges();
                    break;
                case LookupType.Priority:
                    if (Entities.DefectPriorities.Any(m => m.Priority == Value && m.ProjectId == Pid))
                        throw new Exception("This priority was existed");
                    DefectPriority Dp = new DefectPriority();
                    Dp.Priority = Value;
                    Dp.ProjectId = Pid;
                    Entities.DefectPriorities.AddObject(Dp);
                    Entities.SaveChanges();
                    break;
                case LookupType.Version:
                    if (Entities.ProjectVersions.Any(m => m.VersionName == Value && m.ProjectId == Pid))
                        throw new Exception("This version was existed");
                    ProjectVersion Pv = new ProjectVersion();
                    Pv.VersionName = Value;
                    Pv.ProjectId = Pid;
                    Entities.ProjectVersions.AddObject(Pv);
                    Entities.SaveChanges();
                    break;
                default: return false;
            }

            return true;
        }

        public void DeleteLookups(LookupType Type, string Value, String ProjectId)
        {
            DefectTrackingSystemEntities Entities = new DefectTrackingSystemEntities();

            Guid Pid = Guid.Parse(ProjectId);
            switch (Type)
            {
                case LookupType.Type:
                    {
                        if (Entities.Defects.Any(d => d.ProjectId == Pid && d.Type == Value))
                        {
                            throw new Exception("This type is referring other defect, delete defect first !");
                        }
                        DefectType Entity = Entities.DefectTypes.FirstOrDefault(e => e.ProjectId == Pid && e.TypeName == Value);
                        if (Entity != null)
                        {
                            Entities.DefectTypes.DeleteObject(Entity);
                            Entities.SaveChanges();
                        }
                        else
                        {
                            throw new Exception("This type is invalid, lookups will be reloaded");
                        }
                        break;
                    }
                case LookupType.Status:
                    {
                        if (Entities.Defects.Any(d => d.ProjectId == Pid && d.Status == Value))
                        {
                            throw new Exception("This status is referring other defect, delete defect first !");
                        }
                        DefectStatus Entity = Entities.DefectStatus.FirstOrDefault(e => e.ProjectId == Pid && e.Status == Value);
                        if (Entity != null)
                        {
                            Entities.DefectStatus.DeleteObject(Entity);
                            Entities.SaveChanges();
                        }
                        else
                        {
                            throw new Exception("This status is invalid, lookups will be reloaded");
                        }
                        break;
                    }
                case LookupType.Priority:
                    {
                        if (Entities.Defects.Any(d => d.ProjectId == Pid && d.Priority == Value))
                        {
                            throw new Exception("This priority is referring other defect, delete defect first !");
                        }
                        DefectPriority Entity = Entities.DefectPriorities.FirstOrDefault(e => e.ProjectId == Pid && e.Priority == Value);
                        if (Entity != null)
                        {
                            Entities.DefectPriorities.DeleteObject(Entity);
                            Entities.SaveChanges();
                        }
                        else
                        {
                            throw new Exception("This priority is invalid, lookups will be reloaded");
                        }
                        break;
                    }
                case LookupType.Version:
                    {
                        if (Entities.Defects.Any(d => d.ProjectId == Pid && d.Version == Value))
                        {
                            throw new Exception("This version is referring other defect, delete defect first !");
                        }
                        ProjectVersion Entity = Entities.ProjectVersions.FirstOrDefault(e => e.ProjectId == Pid && e.VersionName == Value);
                        if (Entity != null)
                        {
                            Entities.ProjectVersions.DeleteObject(Entity);
                            Entities.SaveChanges();
                        }
                        else
                        {
                            throw new Exception("This version is invalid, lookups will be reloaded");
                        }
                        break;
                    }
                default: break;
            }
        }

        #endregion                   

        #region Project
        /// <summary>
        /// Search Project
        /// </summary>
        /// <param name="Keyword"></param>
        /// <param name="UserName"></param>
        /// <returns></returns>
        public IQueryable<Project> SearchProject(String Keyword, String UserName)
        {
            DefectTrackingSystemEntities Entities = new DefectTrackingSystemEntities();

            Account account = AccountManagement.GetInstance().GetUserByUserName(UserName);

            if (account == null)
            {
                throw new Exception("User Name is not exited");
            }

            if (account.Role == "Project Admin" || account.Role == "Group Admin")
            {
                return from p in Entities.Projects
                       where (p.ProjectName.Contains(Keyword) || p.Description.Contains(Keyword))&&(p.Account.GroupId == account.GroupId)
                       orderby p.ProjectName
                       select p;
            }
            else
            {
                var projects = from pm in Entities.ProjectMembers
                               where pm.AccountId == account.AccountId
                               select pm.Project;
                return projects.Where(p => p.ProjectName.Contains(Keyword) || p.Description.Contains(Keyword)).OrderBy(p => p.ProjectName);
            }
        }

        public void AddProject(ProjectModel Model)
        {
            DefectTrackingSystemEntities Entities = new DefectTrackingSystemEntities();
            Project temp = GetProjectByName(Model.ProjectName, AccountManagement.GetInstance().GetUserByUserName(Model.UserName).GroupId.ToString());
            if (temp != null)
            {
                throw new Exception("Project Name is existed");
            }

            Project P = new Project();
            P.ProjectId = Guid.NewGuid();
            P.ProjectName = Model.ProjectName;
            P.Description = Model.Description;
            P.Creator = AccountManagement.GetInstance().GetUserByUserName(Model.UserName).AccountId;
            P.CreatedDate = DateTime.Now;
            P.IsActive = Model.IsActive;
            Entities.Projects.AddObject(P);
            Entities.SaveChanges();
        }

        public Project GetProjectByName(String ProjectName, String GroupId)
        {
            DefectTrackingSystemEntities Entities = new DefectTrackingSystemEntities();

            Guid Id;
            Guid.TryParse(GroupId, out Id);

            return Entities.Projects.FirstOrDefault(p => p.ProjectName == ProjectName && p.Account.GroupId == Id);
        }

        public void EditProject(String ProjectId, ProjectModel Model)
        {
            DefectTrackingSystemEntities Entities = new DefectTrackingSystemEntities();
            Guid id = Guid.Parse(ProjectId);
            Project P = Entities.Projects.FirstOrDefault(pr => pr.ProjectId == id);
            if (P == null)
            {
                throw new Exception("Project is not existed!");
            }
            P.ProjectName = Model.ProjectName;
            P.Description = Model.Description;
            P.IsActive = Model.IsActive;

            Entities.SaveChanges();
        }

        public ProjectModel GetProjectModel(string ProjectId)
        {
            Project P = GetProjectById(ProjectId);
            ProjectModel Model = new ProjectModel();

            Model.ProjectName = P.ProjectName;
            Model.Description = P.Description;
            Model.UserName = P.Account.UserName;
            Model.IsActive = P.IsActive;

            return Model;
        }

        public Project GetProjectById(String ProjectId)
        {
            DefectTrackingSystemEntities Entities = new DefectTrackingSystemEntities();
            Guid projectId = new Guid();
            try
            {
                projectId = Guid.Parse(ProjectId);
            }
            catch (Exception ex)
            {
            }
            return Entities.Projects.FirstOrDefault(p => p.ProjectId == projectId);
        }

        #region Delete Project
        public void LockProject(string ProjectId)
        {
            DefectTrackingSystemEntities Entities = new DefectTrackingSystemEntities();

            Guid Pid;
            XmlNode NodePros = null;
            if (!Guid.TryParse(ProjectId, out Pid))
            {
                throw new Exception("Project Id is invalid");
            }
            if (!Entities.Projects.Any(m => m.ProjectId == Pid))
            {
                throw new Exception("This project does not exist");
            }
            
            XmlDocument Doc = new XmlDocument();
            if (File.Exists(Path))
            {
                Doc.Load(Path);
                NodePros = Doc.SelectSingleNode("Projects");
            }
            else
            {
                Doc.AppendChild(Doc.CreateXmlDeclaration("1.0", "utf-8", ""));
                NodePros = Doc.CreateNode(XmlNodeType.Element, "Projects", "");
            }
            
            XmlNode NodePro = Doc.CreateNode(XmlNodeType.Element, "Project", "");
            XmlNode NodePid = Doc.CreateNode(XmlNodeType.Element, "ProjectId", "");
            XmlNode NodeDate = Doc.CreateNode(XmlNodeType.Element, "DateDelete", "");
            NodePid.InnerText = ProjectId;
            NodeDate.InnerText = DateTime.Now.AddDays(10).ToString();
            NodePro.AppendChild(NodePid);
            NodePro.AppendChild(NodeDate);
            NodePros.AppendChild(NodePro);
            Doc.AppendChild(NodePros);
            Doc.Save(Path);
            ToggleActiveProject(ProjectId, false);
        }

        public void UnlockProject(string ProjectId)
        {
            string Path = AppDomain.CurrentDomain.BaseDirectory + "Content\\LockProject.xml";
            if (File.Exists(Path))
            {
                XmlDocument Doc = new XmlDocument();
                Doc.Load(Path);
                foreach (XmlNode Node in Doc.SelectSingleNode("Projects"))
                {
                    if (Node.FirstChild.InnerText == ProjectId)
                    {
                        Doc.SelectSingleNode("Projects").RemoveChild(Node);
                        Doc.Save(Path);
                        ToggleActiveProject(ProjectId, true);
                        return;
                    }
                }
            }
        }

        public void ToggleActiveProject(string ProjectId, bool IsActive)
        {
            Guid id = Guid.Parse(ProjectId);
            DefectTrackingSystemEntities Entities = new DefectTrackingSystemEntities();
            Project Pro = Entities.Projects.FirstOrDefault(p => p.ProjectId == id);
            Pro.IsActive = IsActive;
            Entities.SaveChanges();
        }

        public void TriggerDeleteProject()
        {
            List<TempLockProject> Li = GetDeleteProjects();
            if (Li != null)
            {
                foreach (TempLockProject Tp in Li)
                {
                    if (Tp.DeleteDate < DateTime.Now)
                    {
                        DeleteProject(Tp.ProjectId);
                        UnlockProject(Tp.ProjectId);
                    }
                }               
            }
        }

        public void DeleteProject(String ProjectId)
        {

            DefectTrackingSystemEntities Entities = new DefectTrackingSystemEntities();

            Project Pro = Entities.Projects.FirstOrDefault(m => m.ProjectId == new Guid(ProjectId));

            foreach (ProjectMember Mem in Pro.ProjectMembers.ToList())
                Entities.DeleteObject(Mem);

            foreach (Defect Def in Pro.Defects.ToList())
                DefectManagement.GetInstance().DeleteDefect(Def);

            foreach (DefectStatus Ds in Pro.DefectStatus.ToList())
                Entities.DeleteObject(Ds);

            foreach (DefectType Dt in Pro.DefectTypes.ToList())
                Entities.DeleteObject(Dt);

            foreach (DefectPriority Dp in Pro.DefectPriorities.ToList())
                Entities.DeleteObject(Dp);

            foreach (ProjectVersion Pv in Pro.ProjectVersions.ToList())
                Entities.DeleteObject(Pv);

            Entities.DeleteObject(Pro);

            Entities.SaveChanges();

        }

        public List<TempLockProject> GetDeleteProjects()
        {
            List<TempLockProject> Li = new List<TempLockProject>();
            string Path = AppDomain.CurrentDomain.BaseDirectory + "Content\\LockProject.xml";
            if (!File.Exists(Path))
                return Li;
            XmlDocument Doc = new XmlDocument();
            Doc.Load(Path);
            foreach (XmlNode Node in Doc.SelectSingleNode("Projects"))
            {
                TempLockProject Tp = new TempLockProject();
                Tp.ProjectId = Node.FirstChild.InnerText;
                Tp.DeleteDate = Convert.ToDateTime(Node.LastChild.InnerText);
                Li.Add(Tp);
            }
            return Li;
        }
        #endregion

        #endregion

        #region Project Member
        /// <summary>
        /// Get all member of project by ProjectId
        /// </summary>
        /// <param name="projectId">ProjectId</param>
        /// <returns>return list of ProjectMember</returns>
        public IQueryable<ProjectMember> GetProjectMembers(string ProjectId)
        {
            DefectTrackingSystemEntities Entities = new DefectTrackingSystemEntities();

            Guid projectId;

            Guid.TryParse(ProjectId, out projectId);

            return Entities.ProjectMembers.Where(p => p.ProjectId == projectId).OrderBy(m => m.Account.UserName);
        }

        /// <summary>
        /// Get a project member by accountId and projectId
        /// </summary>
        /// <param name="accountId">accountId want get</param>
        /// <param name="projectId">project contain account want get</param>
        /// <returns></returns>
        public ProjectMember GetProjectMember(string accountId, string projectId)
        {
            DefectTrackingSystemEntities Entities = new DefectTrackingSystemEntities();

            try
            {
                Guid AccountId = Guid.Parse(accountId);
                Guid ProjectId = Guid.Parse(projectId);
                ProjectMember member = Entities.ProjectMembers.FirstOrDefault(pm => pm.AccountId == AccountId && pm.ProjectId == ProjectId);
                return member;
            }
            catch (Exception ex)
            {
                return null;
            }
        }

        /// <summary>
        /// Get Assiged Members for Project
        /// </summary>
        /// <param name="ProjectId">ProjectId</param>
        /// <returns>A list of Account</returns>
        public IQueryable<Account> GetAssignedMembers(String ProjectId)
        {
            DefectTrackingSystemEntities Entities = new DefectTrackingSystemEntities();

            Guid projectId;

            Guid.TryParse(ProjectId, out projectId);

            var Accounts = from P in Entities.ProjectMembers
                           where P.ProjectId == projectId && P.IsAssigned == true
                           select P.Account;

            return Accounts;
        }

        /// <summary>
        /// Get all member in a group except member in a project by ProjectId
        /// </summary>
        /// <param name="projectId">ProjectId to except member</param>
        /// <returns>return a list of Account</returns>
        public IQueryable<Account> GetGroupMembersExceptProjectMembers(string projectId)
        {
            DefectTrackingSystemEntities Entities = new DefectTrackingSystemEntities();

            Guid ProjectId = Guid.Parse(projectId);

            Guid GroupId = Entities.Projects.FirstOrDefault(p => p.ProjectId == ProjectId).Account.GroupId;

            var projectMembers = from acc in Entities.Accounts
                                 join proMember in Entities.ProjectMembers
                                 on acc.AccountId equals proMember.AccountId
                                 where proMember.ProjectId == ProjectId
                                 orderby acc.UserName
                                 select acc;

            var groupMember = from member in Entities.Accounts
                              where member.GroupId == GroupId
                              orderby member.UserName
                              select member;

            var GroupMembers = groupMember.Except(projectMembers);

            return GroupMembers;
        }

        /// <summary>
        /// add a member in group to a project
        /// </summary>
        /// <param name="member">a ProjectMember object</param>
        /// <returns>true or false indicate add success or unsuccess</returns>
        public bool AddProjectMember(ProjectMember member)
        {
            DefectTrackingSystemEntities Entities = new DefectTrackingSystemEntities();

            var projectMembers = Entities.ProjectMembers.Where(p => p.AccountId == member.AccountId && p.ProjectId == member.ProjectId);
            if (projectMembers.Count() > 0)
                return false;
            else
            {
                try
                {
                    Entities.ProjectMembers.AddObject(member);
                    Entities.SaveChanges();
                    Account Acc = Entities.Accounts.FirstOrDefault(a => a.AccountId == member.AccountId);
                    EmailNotification.GetIntance().NotifyChangeProjectMember(member.ProjectId.ToString(), Acc.UserName);
                    return true;
                }
                catch (Exception ex)
                {
                    throw ex;
                }
            }
        }

        /// <summary>
        /// remove a project member from a project
        /// </summary>
        /// <param name="member">a ProjectMember object</param>
        /// <returns></returns>
        public bool RemoveProjectMember(ProjectMember member)
        {
            DefectTrackingSystemEntities Entities = new DefectTrackingSystemEntities();

            var projectMembers = Entities.ProjectMembers.Where(p => p.AccountId == member.AccountId && p.ProjectId == member.ProjectId);
            if (projectMembers.Count() > 0)
            {
                try
                {
                    var defect = Entities.Defects.Where(d => (d.Creator == member.AccountId || d.AssignedMember == member.AccountId) && d.ProjectId == member.ProjectId);
                    if (defect.Count() == 0)
                    {
                        Entities.ProjectMembers.DeleteObject(projectMembers.FirstOrDefault());
                        Entities.SaveChanges();
                        Account Acc = Entities.Accounts.FirstOrDefault(a => a.AccountId == member.AccountId);
                        EmailNotification.GetIntance().NotifyChangeProjectMember(member.ProjectId.ToString(), Acc.UserName);
                        return true;
                    }
                    else throw new Exception("You can't remove this user from this project because it has create or assigned to a (some) defect(s) of this project");
                }
                catch (Exception ex) {
                    throw ex;
                }
            }
            return false;
        }

        /// <summary>
        /// toggle assign for a member of project
        /// </summary>
        /// <param name="member">a ProjectMember</param>
        /// <returns></returns>
        public bool ToggleAssignForMember(ProjectMember member)
        {
            DefectTrackingSystemEntities Entities = new DefectTrackingSystemEntities();

            try
            {
                var projectMembers = Entities.ProjectMembers.Where(p => p.AccountId == member.AccountId && p.ProjectId == member.ProjectId);
                if (projectMembers.Count() > 0)
                {
                    if (!member.IsAssigned)
                    {
                        var defect = Entities.Defects.Where(d => d.AssignedMember == member.AccountId && d.ProjectId == member.ProjectId);
                        if (defect.Count() == 0)
                        {
                            projectMembers.FirstOrDefault().IsAssigned = false;
                        }
                        else
                        {
                            throw new Exception("You can't unassign this member to this project because this User has assigned to a (some) defect(s) of this project");
                        }
                    }
                    else projectMembers.FirstOrDefault().IsAssigned = true;
                    Entities.SaveChanges();
                    return true;
                }
                return false;
            }
            catch (Exception ex) {
                throw ex;
            }
        }

        /// <summary>
        /// Search AssignedMember
        /// </summary>
        /// <param name="ProjectId"></param>
        /// <param name="Keyword"></param>
        /// <returns></returns>
        public IQueryable<Account> SearchAssignedMember(string ProjectId, string Keyword)
        {
            var Accounts = GetAssignedMembers(ProjectId);

            return from a in Accounts
                   where (a.Profiles.FirstOrDefault().FirstName + a.Profiles.FirstOrDefault().LastName).Contains(Keyword) ||
                         a.Profiles.FirstOrDefault().Email.Contains(Keyword)
                   select a;
        }

        /// <summary>
        /// Search AssignedMember
        /// </summary>
        /// <param name="ProjectId"></param>
        /// <param name="Keyword"></param>
        /// <returns></returns>
        public IQueryable<Account> SearchProjectMember(string ProjectId, string Keyword)
        {
            var ProjectMembers = GetProjectMembers(ProjectId);

            return from pm in ProjectMembers
                   where (pm.Account.Profiles.FirstOrDefault().FirstName + pm.Account.Profiles.FirstOrDefault().LastName).Contains(Keyword) ||
                         pm.Account.Profiles.FirstOrDefault().Email.Contains(Keyword)
                   select pm.Account;
        }
        #endregion                   
    }

    public class ProjectModel
    {
        [Required]
        [DisplayName("Project name")]
        public string ProjectName { get; set; }

        [DisplayName("Description")]
        public string Description { get; set; }

        [DisplayName("IsActive")]
        public bool IsActive { get; set; }

        [DisplayName("User Name")]
        public String UserName { get; set; }
    }

    public class LookupsModel
    {
        public string[] DefectTypes { get; set; }

        public string[] DefectStatuses { get; set; }

        public string[] DefectPriorities { get; set; }

        public string[] ProjectVersions { get; set; }

        public string ProjectId { get; set; }
    }

    public class TempLockProject
    {
        public string ProjectId { get; set; }
        public DateTime DeleteDate { get; set; }
    }

    public enum LookupType
    {
        Type,
        Version,
        Status,
        Priority,
        None
    }
}